﻿#include "internal.h"
#include <wgal/res.h>
#include <wgal/util.h>
#include <map>
#include <stack>

typedef struct
{
	WGALString path;
	HICON hIcon;
	HICON hIconSm;
} IconResource;

typedef struct
{
	WGALString path;
	HINSTANCE hInst;
} ModuleHandle;

typedef bool(*StringComp)(WGALCString,WGALCString);

typedef std::map<WGALUInt32,IconResource>	IconMap;
typedef std::map<WGALCString,WGALUInt32,StringComp>	IconPathMap;

typedef std::map<WGALUInt32,ModuleHandle>	InstMap;
typedef std::map<WGALCString,WGALUInt32,StringComp>	InstPathMap;
typedef std::pair<WGALUInt32,ModuleHandle>	InstPair;
typedef std::pair<WGALCString,WGALUInt32>	InstPathPair;

typedef std::map<WGALUInt32,WGALUInt32>		ResIdMap;
typedef std::pair<WGALUInt32,WGALUInt32>	ResIdPair;

bool compare_string(WGALCString a, WGALCString b)
{
	return _wcsicmp(a, b) < 0;
}

static WGALUInt32 resId = 0;
static ResIdMap resIdMap;
static IconMap icon;
static IconPathMap icon_path(compare_string);
static InstMap inst;
static InstPathMap inst_path(compare_string);

static std::stack<WGALUInt32> recycledResId;

/* resource id management */
WGALUInt32 GetResourceId(WGALUInt8 type)
{	
	if(type == WGAL_RESTYPE_UNKNOWN)
		return ID_NONE;

	WGALUInt32 id = ID_NONE;

	if(!recycledResId.empty())
	{
		// we have recycled id; reuse it
		id = recycledResId.top();
		recycledResId.pop();
	}
	else
	{
		// get new id
		if(resId >= 0x00FFFFFF)
		{
			// hit the limit; there must be a leak.
			return ID_NONE;
		}

		id = ++resId;
	}

	id |= type << 24;

	// increment reference count
	AddIdRefCount(id);

	return id;
}

WGALUInt32 AddIdRefCount(WGALUInt32 resId)
{
	return ++resIdMap.insert(
		ResIdPair(resId,0)).first->second;
}

WGALUInt32 FreeResourceId(WGALUInt32 resId)
{	
	ResIdMap::iterator it = resIdMap.find(resId);
	
	if(it != resIdMap.end())
	{
		WGALUInt32 ref = 0;

		if((ref = --it->second) == 0)
		{
			resIdMap.erase(it);
			recycledResId.push(resId & 0x00FFFFFF);
		}

		return ref;
	}

	return 0;
}

/* external resource module management */
WGALAPI WGALUInt32 wgalLoadResourceModule(WGALCString module)
{
	HINSTANCE hInst = NULL;

	if(!module)
		module = L"";

	InstPathMap::iterator it = inst_path.find(module);

	if(it != inst_path.end())
	{
		// exists
		AddIdRefCount(it->second);
		return it->second;
	}

	// not loaded
	if(!IS_STRING_EMPTY(module))
	{
		// try loading from specified path
		hInst = LoadLibraryExW(module, NULL, LOAD_LIBRARY_AS_DATAFILE);
	}
	else
	{
		// requested self handle
		hInst = GetModuleHandle(NULL);
	}

	if(!hInst)
		return ID_NONE;
	
	size_t len = lstrlenW(module);
	WGALString w_str = 
		(WGALString)malloc(sizeof(WGALChar) * (len + 1));
	ZeroMemory(w_str, sizeof(WGALChar) * (len + 1));
	lstrcpyW(w_str, module);

	WGALUInt32 id = GetResourceId(WGAL_RESTYPE_MODULE);
	ModuleHandle res = {w_str, hInst};

	inst_path.insert(InstPathPair(w_str, id));
	inst.insert(InstPair(id, res));

	return id;
}

WGALAPI WGALUInt32 wgalFreeResourceModule(WGALUInt32 module)
{
	if(module == ID_NONE)
		return 0;

	InstMap::iterator it = inst.find(module);

	if(it != inst.end())
	{
		WGALUInt32 ref = FreeResourceId(module);

		if(ref == 0)
		{
			if(!IS_STRING_EMPTY(it->second.path))
				FreeLibrary(it->second.hInst);
			inst_path.erase(it->second.path);
			free(it->second.path);
			inst.erase(it);
		}

		return ref;
	}

	return 0;
}

WGALAPI WGALVoid wgalFreeResourceModuleAll()
{
	for(InstMap::iterator it = inst.begin();
		it != inst.end();
		it++)
	{
		while(FreeResourceId(it->first) != 0);

		if(IS_STRING_EMPTY(it->second.path))
			FreeLibrary(it->second.hInst);

		free(it->second.path);
	}
	inst.clear();
	inst_path.clear();
}

HINSTANCE GetModuleInstanceHandle(WGALUInt32 module)
{
	InstMap::iterator it = inst.find(module);

	if(it != inst.end())
	{
		// exists
		return it->second.hInst;
	}

	// not loaded
	return NULL;
}

WGALAPI WGALUInt32 wgalLoadIcon(WGALCString path)
{
	CHECK_INITIALIZED(wgal_init);

	if(!path)
		return ID_NONE;

	IconPathMap::iterator it = icon_path.find(path);

	if(it != icon_path.end())
	{
		WGALUInt32 id = it->second;
		AddIdRefCount(id);
		return id;
	}
	else
	{
		HICON hIconSm = 
			(HICON)LoadImageW(NULL, path, IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
		HICON hIcon = 
			(HICON)LoadImageW(NULL, path, IMAGE_ICON, 32, 32, LR_LOADFROMFILE);

		if(hIcon)
		{
			WGALUInt32 id = GetResourceId(WGAL_RESTYPE_ICON);

			size_t len = lstrlenW(path);
			WGALString w_str = (WGALString)malloc(sizeof(WGALChar) * (len + 1));
			lstrcpyW(w_str, path);

			IconResource ico = {w_str, hIcon, hIconSm};
			icon[id] = ico;
			icon_path[w_str] = id;

			return id;
		}
	}

	return ID_NONE;
}

WGALAPI WGALUInt32 wgalLoadIconFromResource(WGALUInt32 module,
											WGALCString name)
{
	CHECK_INITIALIZED(wgal_init);

	WGALString w_str = NULL;	

	if(IS_INTRESOURCE(name))
	{
		size_t len = 8 + 4;
		w_str = (WGALString)malloc(sizeof(WGALChar) * (len + 3));
		// ZeroMemory for security
		ZeroMemory(w_str, sizeof(WGALChar) * (len + 3));
		wsprintfW(w_str, L"%08x?#%04x", module, name);
	}
	else
	{
		size_t len = lstrlenW(name) + 8;
		w_str = (WGALString)malloc(sizeof(WGALChar) * (len + 2));
		// ZeroMemory for security
		ZeroMemory(w_str, sizeof(WGALChar) * (len + 2));
		wsprintfW(w_str, L"%08x?%s", module, name);
	}

	IconPathMap::iterator it = icon_path.find(w_str);

	if(it != icon_path.end())
	{
		free(w_str);
		WGALUInt32 id = it->second;
		AddIdRefCount(id);
		return id;
	}
	else
	{
		HINSTANCE hInst = GetModuleInstanceHandle(module);

		if(hInst)
		{
			HICON hIconSm = 
				(HICON)LoadImageW(hInst, name, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
			HICON hIcon = 
				(HICON)LoadImageW(hInst, name, IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);

			if(hIcon)
			{
				WGALUInt32 id = GetResourceId(WGAL_RESTYPE_ICON);

				IconResource ico = {w_str, hIcon, hIconSm};
				icon[id] = ico;
				icon_path[w_str] = id;

				return id;
			}
		}
	}
	
	return ID_NONE;
}

WGALAPI WGALVoid wgalFreeIcon(WGALUInt32 iconId)
{
	CHECK_INITIALIZED(wgal_init);

	IconMap::iterator it = icon.find(iconId);
	
	if(it != icon.end())
	{
		if(FreeResourceId(iconId) == 0)
		{
			DestroyIcon(it->second.hIcon);
			//if(it->second.second)
			DestroyIcon(it->second.hIconSm);
			icon_path.erase(it->second.path);
			free(it->second.path);			
			icon.erase(it);			
		}
	}
}

WGALAPI WGALVoid wgalFreeIconAll()
{
	CHECK_INITIALIZED(wgal_init);

	for(IconMap::iterator it = icon.begin();
		it != icon.end();
		it++)
	{
		while(FreeResourceId(it->first) != 0);

		DestroyIcon(it->second.hIcon);
		//if(it->second.second)
		DestroyIcon(it->second.hIconSm);
		free(it->second.path);
	}

	icon.clear();
	icon_path.clear();
}

HICON GetIconLarge(WGALUInt32 icon_id)
{
	if(icon_id == ID_NONE)
		return NULL;

	IconMap::iterator it = icon.find(icon_id);

	if(it == icon.end())
		return NULL;

	return it->second.hIcon;
}

HICON GetIconSmall(WGALUInt32 icon_id)
{
	if(icon_id == ID_NONE)
		return NULL;

	IconMap::iterator it = icon.find(icon_id);

	if(it == icon.end())
		return NULL;

	return it->second.hIconSm;
}